<?php

require_once 'Controllers/Room.php';

class Game extends Room {

    private $board = null;
    protected $players = null;
    private $id;
    private $tmpDstPile = null;
    private $tmpSrcPile = null;
    private $tmpCard = null;
    private $protectedFromAttackPlayers = array();

    public function __construct($id = null, $players = array(), $board =null) {

        if ($id == null) {
            $db = DBUtils::Create();
            $id = $db->newGame();
        }
        $this->id = $id;

        $this->players = $players;
        if ($board)
            $this->board = $board;
        else
            $this->board = new Board();
    }

    public function getRoomCardsData($data = array()) {
        $cards = $this->getAllBoardCards(true);
        $cards_data = array();

        //$aiu = getAlternativeImages($cards);
        foreach ($cards as $card) {
            $cards_data[$card->getValue()] = $card->getFullBasicData($this);
            //$cards_data[$card->getValue()]['alternativeImageUrl'] = $aiu[$card->getKey()];
        }
        return $cards_data;
    }

    public function getData() {
        $new_data = array();
        $new_data['roomid'] = $this->getId();
        $isGameOver = $this->isGameOver();
        $new_data['isGameOver'] = $isGameOver;

        if ($isGameOver) {
            $name = getUserId();
            $game_over_stats = $this->getGameOverStats($name);
            $new_data['gameoverdata'] = $game_over_stats;
        }

        $coinTokensRequired = false;
        $cards = $this->getAllBoardCards(true);
        foreach ($cards as $card) {
            if ($card instanceof iCoinTokensRequired) {
                $coinTokensRequired = true;
            }
        }

        $new_data['cointokensrequired'] = $coinTokensRequired;


        return $new_data;
    }

    public function getPlayerData($data, $name = null) {

        $player = $this->getCurrentPlayer($name);
        $data['player'] = $player->getPlayerData($this, $data);
        return $data;
    }

    public function getPublicPlayerData($name = null) {
        $player = $this->getCurrentPlayer($name);
        $data = $player->getPublicPlayerData($this);

        return $data;
    }

    public function getPublicPlayersData() {
        $data = array();
        $otherPlayersNames = $this->getPlayersNames();
        $data['players'] = array();
        foreach ($otherPlayersNames as $op_name) {
            $op = $this->getCurrentPlayer($op_name);
            $pubData = $op->getPublicPlayerData($this);
            $data['players'][] = $pubData;
            if ($pubData['victorytokens'] > 0)
                $data['displayvictorytokens'] = true;
            if ($pubData['pirateshiptokens'] > 0)
                $data['displaypirateshiptokens'] = true;
            if ($pubData['cointokens'] > 0)
                $data['displaycointokens'] = true;
        }
        return $data;
    }

    public function getBoardData($data) {
        $data = $this->board->getBoardData($this, $data);
        return $data;
    }

    public function save() {
        $db = DBUtils::Create();
        $i = 0;
        foreach ($this->players as $p) {
            $p->save($this->id, $i);
            $i+=1;
        }
        $res = $this->board->save($this->id);
        return $res;
    }

    public function getId() {
        return $this->id;
    }

    public function addPlayer($player) {
        $this->players[] = $player;
    }

    public function setUniqueid($name = null) {
        $player = $this->getCurrentPlayer($name);
        $player->setUniqueid();
    }

    public function getPlayerGender($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getGender();
    }

    private function getCurrentPlayer($name = null) {
        if ($name == null)
            return $this->players[0];
        else {
            foreach ($this->players as $player) {
                if ($player->getName() == $name) {
                    return $player;
                }
            }
            throw new Exception("$name: is not a valid player in this game");
        }
    }

    public function getCurrentPlayerTurnNumber($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getTurnNumber();
    }

    public function getCurrentPlayerName($name=null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getName();
    }

    public function getNextPlayerName($name = null) {
        if ($name == null) {
            $i = 1 % (sizeof($this->players));
            return $this->players[$i]->getName();
        } else {
            for ($i = 0; $i < sizeof($this->players); $i++) {
                if ($this->players[$i]->getName() == $name) {
                    $j = ($i + 1) % (sizeof($this->players));
                    return $this->players[$j]->getName();
                }
            }
            return null;
        }
    }

    public function getPreviousPlayerName($name = null) {
        if ($name == null) {
            $i = sizeof($this->players) - 1;
            return $this->players[$i]->getName();
        } else {
            for ($i = 0; $i < sizeof($this->players); $i++) {
                if ($this->players[$i]->getName() == $name) {
                    $j = $i - 1;
                    if ($j == -1)
                        $j = sizeof($this->players) - 1;
                    return $this->players[$j]->getName();
                }
            }
            return null;
        }
    }

    public function protectFromAttack($name) {
        $this->protectedFromAttackPlayers[] = $name;
    }

//if it is an attack, we might want to skip the name of the defender if he has moat in revealed cards or lighthouse in duration Affects
    private function checkIfPlayerProtectedAgainstAttacks($name) {
        $player = $this->getCurrentPlayer($name);

        if (in_array($name, $this->protectedFromAttackPlayers))
            return true;

        $lighthouse = new Lighthouse();
        $lighthouse_key = $lighthouse->getKey();
        $durationAffects = $player->getDurationAffects();

        foreach ($durationAffects as $card_arr) {
            $card = $card_arr['card'];
            if (!$card)
                continue;
            if (!is_object($card))
                $card = n2c($card);
            if ($card->getKey() == $lighthouse_key) {
                ScreenDisplay::addScreenMessage($this, true, "are protected by " . nicePrint($lighthouse), "is protected by " . ScreenDisplay::$LMSG, $name);
                return true;
            }
        }

        return false;
    }

    public function getOtherPlayersNames($isAttack = false, $name = null) {

        $name = $this->getCurrentPlayerName($name);
        $o_names = array();
        for ($i = 0; $i < sizeof($this->players); $i++) {
            $o_name = $this->players[$i]->getName();
            if (($o_name == $name) || ($isAttack && $this->checkIfPlayerProtectedAgainstAttacks($o_name))) {
                continue;
            }
            $o_names[] = $o_name;
        }
        return $o_names;
    }

    /* public function getOtherPlayersNames($name = null) {
      return $this->getOtherPlayersNames(false, $name);
      } */

    public function getPlayersNames($isAttack = false) {
        $names = array();
        for ($i = 0; $i < sizeof($this->players); $i++) {
            $name = $this->players[$i]->getName();
            if ($i != 0 && $isAttack && $this->checkIfPlayerProtectedAgainstAttacks($name)) {
                continue;
            }
            $names[] = $name;
        }
        return $names;
    }

    public function getPlayerName($name = null) {
        $player = $this->getCurrentPlayer();
        return $player->getName();
    }

    public function action($card_name) {
        $player = $this->getCurrentPlayer();
        $player->action($card_name, $this);
        if ($player->getPhase() == Phase_Action && $player->getActions() == 0) {
            $this->buyPhase();
        }
    }

    public function payment($card_name) {
        $player = $this->getCurrentPlayer();
        $res = $player->payment($card_name, $this);
    }

    public function buy($card_name) {
        $player = $this->getCurrentPlayer();
        $oldPhase = $player->getPhase();
        $card = $player->buy($card_name, $this);
        if ($oldPhase == Phase_DigitalStoreBuy) {
            #do nothing, just dont end turn
        } else if ($player->getPhase() == Phase_Buy && $player->getBuys() == 0) {
            //  $this->endTurn();
        }
        return $card;
    }

    public function buyCardFromBlackMarket($card) {
        $player = $this->getCurrentPlayer();
        $card = prepare($card);
        $card = $this->board->peekCardFromBlackMarket($card);
        return $player->buyCardFromBlackMarket($card, $this);
    }

    public function buyPhaseAndPlayAllTreasures() {
        $player = $this->getCurrentPlayer();
        $player->buyPhaseAndPlayAllTreasures($this);
    }

    public function playCoinTokens() {
        $player = $this->getCurrentPlayer();
        $player->playCoinTokens($this);
    }

    public function buyPhase() {
        $player = $this->getCurrentPlayer();
        $player->buyPhase($this);
    }

    public function startGame($decks, $required_cards, $forceColony, $forceDarkAges, $debugGame) {
        shuffle($this->players);

        $isValidBoard = $this->board->setNewBoard($this, $decks, $required_cards, self::$ALL_KINGDOM, $forceColony, $forceDarkAges, $debugGame);
        if (!$isValidBoard) {
            return false;
        }
        $boardCards = array();
        foreach ($this->getBoardCardsByPrice() as $bc)
            if ($bc instanceof iKingdom)
                $boardCards[] = $bc;

        ScreenDisplay::addScreenMessage($this, true, "Game kingdom cards: " . nicePrint($boardCards), "Game kingdom cards: " . ScreenDisplay::$LMSG, null, true, false, true);
        ScreenDisplay::addScreenMessage($this, true, NEW_LINE, NEW_LINE);

        $isDarkAges = $this->board->isDarkAges();
        if ($isDarkAges) {
            foreach ($this->players as $player) {
                $player->setDeckWithDarkAges();
            }
        } else if ($debugGame) {
            foreach ($this->players as $player) {
                $player->setDeckForTest();
            }
        } else {
            foreach ($this->players as $player) {
                $player->setDeck();
            }
        }

#printing turn order message
        $playersOrder = "";
        if (sizeof($this->players) == 1) {
            $playersOrder = "You play a solitare game";
        } else {
            for ($i = 1; $i < sizeof($this->players); $i+=1) {
                if ($i < sizeof($this->players) - 1)
                    $playersOrder .= ", " . $this->players[$i]->getName();
                else
                    $playersOrder .= " and then " . $this->players[$i]->getName();
            }
            $playersOrder = "Turn order is: " . $this->players[0]->getName() . "" . $playersOrder;
        }

        ScreenDisplay::addScreenMessage($this, true, $playersOrder, $playersOrder, null, true, false, true);

        foreach ($this->players as $player) {
            $player->startGame($this);
        }



        for ($i = 1; $i < sizeof($this->players); $i+=1) {
            setRoom($this, $this->players[$i]->getName());
            $this->players[$i]->setPhase($this, Phase_Sleep);
        }

        setRoom($this, $this->players[0]->getName());
        $this->players[0]->startTurn($this);
        return true;
    }

    public function endTurn() {
        $current_player = $this->getCurrentPlayer();

        if ($this->isGameOver()) {
            $this->gameOver($current_player->getName());
            return;
        }


        if ($current_player->getName() == getUserId()) {
            $res = $current_player->endTurn($this);
            if ($res === true) {
                array_shift($this->players);
                $this->players[] = $current_player;
                $new_player = $this->getCurrentPlayer();
                $new_player->startTurn($this);
            } else if ($res === OUTPOST_DONT_CHANGE_PLAYER) {
                //in this case we should have a card that will make the start turn instead of the game
                $current_player->startTurn($this);
            } else if ($res === POSSESED_TURN) {
                $nextPlayer = $this->getNextPlayerName($current_player->getName());
                $possessingPlayer = new PossessingPlayer($current_player, $nextPlayer);
                $possessingPlayer->startTurn($this);
            }
        }
    }

    public function addPossessedTurn($name = null) {
        $player = $this->getCurrentPlayer($name);
        $player->addPossessedTurn();
    }

    public function cardActionFinished($name = null) {
        $this->setPhase(Phase_Action, $name);
        $player = $this->getCurrentPlayer($name);
        $player->actionHelper($this);
    }

    public function cardTrashFinished($name = null, $undoBlock = true) {
        if ($undoBlock) {
            $this->undoBlock($name);
        }
    }

    public function cardReactionFinished($name = null, $undoBlock = true) {
        if ($undoBlock) {
            $this->undoBlock($name);
        }
    }

    public function cardPaymentFinished($name = null) {
        $this->setPhase(Phase_Payment, $name);
        $player = $this->getCurrentPlayer($name);
        $player->paymentHelper($this);
    }

    public function cardCleanupFinished($name = null) {
        $this->setPhase(Phase_Sleep, $name);
    }

    public function addActions($actions_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addActions($actions_num);
    }

    public function addBuys($buys_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addBuys($buys_num);
    }

    public function addMoney($price, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addMoney($price);
    }

    public function addVictoryTokens($tokens_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addVictoryTokens($tokens_num);
    }

    public function getOverpayAmount($card_name, $name = null, $allowZeroOverpay = false) {
        $player = $this->getCurrentPlayer($name);
        return $player->getOverpayAmount($this, $card_name, $allowZeroOverpay);
    }

    public function getCoinTokens($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getCoinTokens();
    }

    public function addCoinTokens($tokens_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addCoinTokens($tokens_num);
    }

    public function addRuins() {
        $this->board->addRuins($this);
        return true;
    }

    public function addMadman() {
        $this->board->addMadman($this);
        return true;
    }

    public function addKnights() {
        $this->board->addKnights($this);
        return true;
    }

    public function addMercenary() {
        $this->board->addMercenary($this);
        return true;
    }

    public function addSpoils() {
        $this->board->addSpoils($this);
        return true;
    }

    public function addPotion() {
        $this->board->addPotion($this);
        return true;
    }

    public function addPrizes() {
        $this->board->addPrizes($this);
    }

    public function addBaneCard() {
        $this->board->addBaneCard($this);
    }

    public function addBlackMarket() {
        $this->board->addBlackMarket($this);
    }

    public function attack($attackingCard) {
        $player = $this->getCurrentPlayer();
        $this->playAllReactions($player, Attack, $attackingCard);
    }

    public function playAllReactions($player, $action, $card) {
        $name = $player->getName();
        $others = $this->getOtherPlayersNames(false, $name);

        $otherStates = array();
        foreach ($others as $other_name) {

            $other_state = $this->getPhase($other_name);
            $otherStates[$other_name] = $other_state;
            $this->setPhase(Phase_Sleep, $other_name);
        }
        // current player's reactions
        $this->playReactions($player, Yourself, $action, $card);

        foreach ($others as $other_name) {
            $this->setPhase($otherStates[$other_name], $other_name);
        }

        // other players' reactions
        $oldPhase = $this->getPhase($name);
        $this->setPhase(Phase_Sleep, $name);


        foreach ($others as $reactorName) {
            $reactor = $this->getCurrentPlayer($reactorName);
            $this->playReactions($reactor, SomeoneElse, $action, $card);
        }


        $this->setPhase($oldPhase, $name);
    }

    private function playReactions($player, $actor, $action, $actingCard) {
        $name = $player->getName();
        $reactions = array();
        $fecReactions = array(); // reaction with a "for each card" limit
// get relevant cards
        $piles = array(
            PlayHand => $player->getPlayHand(),
            PlayArea => $player->getPlayArea(),
            DurationPileCards => $player->getDurationAffects(),
            BoardCards => $this->getAllBoardCards(false, false));

        foreach ($piles as $pileName => $pile) {
            foreach ($pile as $reactionCard) {

                //card should not react to itself:
                if ($reactionCard === $actingCard) {
                    continue;
                }
                if (!($reactionCard instanceof iReactingCard)) {
                    continue;
                }
                if ($reactionCard->getConditions()->isEmptyReaction) {
                    continue;
                }
                if (!$reactionCard->isReactRelevant($this, $actingCard, $name)) {
                    continue;
                }

                if ((in_array($action, $reactionCard->getConditions()->action)) &&
                        (in_array($actor, $reactionCard->getConditions()->actor)) &&
                        ( in_array($pileName, $reactionCard->getConditions()->cardLocation))) {
                    if ($reactionCard->getConditions()->limit != ForEachCard) {
                        $fecReactions[] = $reactionCard;
                    } else {
                        $reactions[] = $reactionCard;
                    }
                }
            }
        }

        $reactions = array_merge(getUniqueCards($fecReactions), $reactions);
//we call the reaction and then once again ask if the user wants to play reactions
        $reactions[] = new Option(1, 'None');
        while (sizeof($reactions) > 1) {
            #if there is only 1 reaction, and its mandatory, play it and break the loop
            if (sizeof($reactions) == 2) {
                $only_reaction_card = array_shift($reactions);
                if ($only_reaction_card->getConditions()->isMandatory) {
                    $only_reaction_card->react($this, $actingCard, $name);
                    break;
                } else {
                    array_unshift($reactions, $only_reaction_card);
                }
            }
            $output = MySocket::GetUserReply($this, Input_ChooseOne, $reactions, 1, 1, null, "With which card do you want to react?", $name);
            $card_name = $output[0];
            if ($card_name == "None") {
                if (!$this->hasMandatoryReactions($reactions)) {
                    break;
                } else {
                    ScreenDisplay::addScreenMessage($this, true, "Not all mandatory cards have been played", null, $name, true, true, true);
                    continue;
                }
            }
            $card = n2c($card_name);
            $player->pushCardToReactions($card);

            //adding card to undo stack, currently no undos possible
            $undoParams = array();
            list($validUndo, $undoParams) = $card->undoParamsReaction($undoParams);
            $player->addUndoAction(UNDO_PLAY_REACTION, $undoParams, 0);

            $card->react($this, $actingCard, $name);
            if ($card->getConditions()->limit != UnlimitedReaction) {
                // remove card from reactions array
                $ind = 0;
                foreach ($reactions as $reactionCard) {
                    if ($reactionCard->getKey() == $card->getKey()) {
                        array_splice($reactions, $ind, 1);
                        break;
                    }
                    $ind += 1;
                }
            }
        }
        return;
    }

    private function hasMandatoryReactions($cards) {
        foreach ($cards as $card) {
            if ($card instanceof Option)
                continue;
            if ($card->getConditions()->isMandatory) {
                return true;
            }
        }
        return false;
    }

    public function getVictoryTokens($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getVictoryTokens();
    }

    public function getPirateShipTokens($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPirateShipTokens();
    }

    public function addPirateShipToken($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->addPirateShipToken();
    }

    public function drawCards($cards_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->drawCards($this, $cards_num);
    }

    public function peekCardFromDrawDeck($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->peekCardFromDrawDeck($this);
    }

    public function removeCardsFromDrawDeck($cards_num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->removeCardsFromDrawDeck($this, $cards_num);
    }

    public function removeCardFromDrawDeck($name = null) {
        $cards = $this->removeCardsFromDrawDeck(1, $name);
        if (sizeof($cards) == 0) {
            return null;
        }
        return $cards[0];
    }

    public function removeCardFromPlayHand($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->removeCardFromPlayHand($card);
    }

    public function removeBottomDrawDeckCard($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->removeBottomDrawDeckCard($this);
    }

    public function removeTopBlackMarketCards($num) {
        return $this->board->removeTopBlackMarketCards($num);
    }

    public function putCardOnBottomOfBlackMarket($card) {
        $card = prepare($card);
        return $this->board->putCardOnBottomOfBlackMarket($card);
    }

    public function putCardOnBottomOfDrawDeck($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardOnBottomOfDrawDeck($card);
    }

    public function cardGained($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        $player->cardGained($this, $card);
    }

    public function afterCardGained($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        $player->afterCardGained($this, $card);
    }

    public function discardPlayHand($name = null) {
        $playHand = $this->getPlayHand($name);
        $discardedCards = array();
        foreach ($playHand as $card) {
            $discardedCards[] = $this->moveCardFromPileToPile($card, PlayHand, DiscardPile, $name);
        }
        return $discardedCards;
    }

    public function discardCardsFromDrawDeck($num, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->discardCardsFromDrawDeck($this, $num);
    }

    public function discardTempDiscardCards($cards, $name = null) {
        $res = array();
        foreach ($cards as $card) {
            $c = $this->moveCardFromPileToPile($card, NOPILE, DiscardPile, $name);
            if ($c)
                $res[] = $c;
        }
        return $res;
    }

    public function putCardsInPlayHand($cards, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->putCardsInPlayHand($cards);
    }

    public function getCardInHand($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->getCardInHand($card);
    }

    public function getDurationAffects($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getDurationAffects();
    }

    public function putCardOnTopOfDrawDeck($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardOnTopOfDrawDeck($card);
    }

    public function putCardInDiscardDeck($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardInDiscardDeck($card);
    }

    public function putCardInPlayArea($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardInPlayArea($card);
    }

    public function putCardInPlayHand($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardInPlayHand($card);
    }

    public function getTrash() {
        return $this->board->getTrash();
    }

    public function getPrizes() {
        return $this->board->getPrizes();
    }

    public function getTopKnight() {
        return $this->board->getTopKnight();
    }

    public function shuffleDrawDeck($name = null) {
        $player = $this->getCurrentPlayer($name);
        $player->shuffleDrawDeck();
    }

    public function setDestinationPile($pile) {
        $this->tmpDstPile = $pile;
    }

    public function setTempCard($card) {
        $this->tmpCard = $card;
    }

    public function moveCardFromPileToPile($card, $srcPile, $dstPile, $name = null) {
        $this->tmpSrcPile = $srcPile;
        $this->tmpDstPile = $dstPile;


        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        $newCard = false;

        if ($this->tmpSrcPile == BoardCards ||
                $this->tmpSrcPile == Trash ||
                $this->tmpSrcPile == Prizes ||
                $this->tmpSrcPile == BlackMarketPile ||
                $this->tmpSrcPile == Spoils ||
                $this->tmpSrcPile == Mercenary ||
                $this->tmpSrcPile == Madman
        ) {
            $newCard = true;
            $card = $this->board->getCardFromPile($card, $this->tmpSrcPile);
        } elseif ($this->tmpSrcPile == NOPILE) {
            //do nothing, this card should be 'in the air'
        } else {
            $card = $player->getCardFromPile($card, $this->tmpSrcPile);
        }

        $this->tmpCard = $card;

        if ($this->tmpCard) {

            if ($newCard) {
                $this->cardGained($card, $name);
                $this->playAllReactions($player, BoardCards, $this->tmpCard);
            }

            if ($this->tmpDstPile == BoardCards) {
                $this->board->putCardtoPile($this->tmpCard, $this->tmpDstPile);
            } elseif ($this->tmpDstPile == Trash) {
                $this->board->putCardtoPile($this->tmpCard, $this->tmpDstPile);
                $this->tmpCard->trash($this, $name);
                $this->playAllReactions($player, Trashed, $this->tmpCard);
            } elseif ($this->tmpDstPile == Spoils) {
                $this->board->putCardtoPile($this->tmpCard, $this->tmpDstPile);
            } elseif ($this->tmpDstPile == Madman) {
                $this->board->putCardtoPile($this->tmpCard, $this->tmpDstPile);
            } else if ($this->tmpDstPile != null) {
                $player->putCardToPile($this->tmpCard, $this->tmpDstPile);
                if (($this->tmpDstPile == DiscardPile) && ($this->tmpSrcPile == PlayHand || $this->tmpSrcPile == DrawPile || $this->tmpSrcPile == NOPILE)) {
                    $this->tmpCard->discard($this, $name);
                }
            }

            if ($newCard) {
                $this->afterCardGained($card, $name);
            }
        }

        return $this->tmpCard;
    }

    //this function is similar to the regular moveCardFromPileToPile, but it does not trigger 
    //any reactions in cards, it is adviced to use it only in undo code.
    public function undo_func_moveCardFromPileToPile($card, $srcPile, $dstPile, $name = null) {

        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);

        if ($srcPile == BoardCards ||
                $srcPile == Trash ||
                $srcPile == Prizes ||
                $srcPile == BlackMarketPile ||
                $srcPile == Spoils ||
                $srcPile == Mercenary ||
                $srcPile == Madman
        ) {

            $card = $this->board->getCardFromPile($card, $srcPile);
        } elseif ($srcPile == NOPILE) {
            //do nothing, this card should be 'in the air'
        } else {
            $card = $player->getCardFromPile($card, $srcPile);
        }


        if ($card) {

            if ($dstPile == BoardCards) {
                $this->board->putCardtoPile($card, $dstPile);
            } elseif ($dstPile == Trash) {
                $this->board->putCardtoPile($card, $dstPile);
            } elseif ($dstPile == Spoils) {
                $this->board->putCardtoPile($card, $dstPile);
            } elseif ($dstPile == Madman) {
                $this->board->putCardtoPile($card, $dstPile);
            } elseif ($dstPile == Mercenary) {
                $this->board->putCardtoPile($card, $dstPile);
            } elseif ($dstPile == Prizes) {
                $this->board->putCardtoPile($card, $dstPile);
            } else if ($dstPile != null) {
                $player->putCardToPile($card, $dstPile);
            }
        }

        return $card;
    }

    public function undoBlock($name = null) {
        $player = $this->getCurrentPlayer($name);
        $player->addUndoAction(UNDO_BLOCK, array(), false);
    }

    public function getTopBlackMarketCards($num) {
        return $this->board->getTopBlackMarketCards($num);
    }

    public function peekCardFromBoard($card) {
        $card = prepare($card);
        return $this->board->peekCard($card);
    }

    public function getPlayHand($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPlayHand();
    }

    public function getDiscardDeck($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getDiscardDeck();
    }

    public function getPhase($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPhase();
    }

    public function setPhase($phase, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->setPhase($this, $phase);
    }

    public function putCardInNativeVillageDeck($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->putCardInNativeVillageDeck($card);
    }

    public function PutCardsFromNativeVillageDeckToHand($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->PutCardsFromNativeVillageDeckToHand();
    }

    public function PutCardsFromHavenDeckToHand($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->PutCardsFromHavenDeckToHand();
    }

    public function getBaneCard() {
        return $this->board->getBaneCard();
    }

    public function putCardInTrash($card, $name = null) {
        $card = prepare($card);
        return $this->moveCardFromPileToPile($card, NOPILE, Trash, $name);
    }

    public function putCardInTrashDirectly_UseInCaution($card) {
        $card = prepare($card);
        return $this->board->putCardInTrash($card);
    }

    public function putDrawPileInDiscard($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->putDrawPileInDiscard();
    }

    public function getTotalCardsNum($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getTotalCardsNum();
    }

    public function getAllBoardCards($getVirtualCards = false, $withBlackBarket = true) {
        $allCards = $this->board->getAllBoardCards($getVirtualCards, $withBlackBarket);
        foreach ($this->players as $player) {
            $player_cards = $player->getAllCards();
            foreach ($player_cards as $card) {
                $allCards[$card->getKey()] = $card;
            }
        }
        return $allCards;
    }

    public function getAllBoardCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getAllBoardCardsByPrice($this, $min_price, $max_price);
    }

    public function getTrashCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getTrashCardsByPrice($this, $min_price, $max_price);
    }

    public function getAllBoardIncludingEmptyPiles($includeBlackMarket = false) {
        return $this->board->getAllBoardIncludingEmptyPiles($this, $includeBlackMarket);
    }

    public function getBoardCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getBoardCardsByPrice($this, $min_price, $max_price);
    }

    public function getBoardVictoryCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getBoardVictoryCardsByPrice($this, $min_price, $max_price);
    }

    public function getBoardTreasureCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getBoardTreasureCardsByPrice($this, $min_price, $max_price);
    }

    public function getBoardActionCardsByPrice($min_price = null, $max_price = null) {
        if (!$min_price)
            $min_price = new Price ();
        if (!$max_price)
            $max_price = new Price(PHP_INT_MAX, PHP_INT_MAX);
        return $this->board->getBoardActionCardsByPrice($this, $min_price, $max_price);
    }

    public function getPlayArea($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPlayArea();
    }

    public function getPlayHandSize($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPlayHandSize();
    }

    public function getDrawDeckSize($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getDrawDeckSize();
    }

    public function getDrawDeck($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getDrawDeck();
    }

    public function getDiscardDeckSize($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getDiscardDeckSize();
    }

    public function getGainedCards($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getGainedCards();
    }

    public function getBoughtCards($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getBoughtCards();
    }

    public function getPlayedCards($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getPlayedCards();
    }

    public function getContrabandCards($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getContrabandCards();
    }

    public function contrabandCard($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->ContrabandCard($card);
    }

    public function resolveCardsStack($numOfCards, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $player->resolveCardsStack($this, $numOfCards);
    }

    public function pushCardToStack($card, $isRealCard = 1, $name = null, $validUndo = true) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->pushCardToStack($card, $isRealCard, $validUndo);
    }

    public function pushCardToDurationAffects($card, $quantity, $isRealCard, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->pushCardToDurationAffects($card, $quantity, $isRealCard);
    }

    public function pushCardToInfluence($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        $card = prepare($card);
        return $player->pushCardToInfluence($card);
    }

    public function embargoCard($card) {
        $card = prepare($card);
        $this->board->embargoCard($card);
        return $card;
    }

    public function getEmbargoedCards() {
        return $this->board->getEmbargoedCards();
    }

    public function addCardToTradeRoute($card) {
        $card = prepare($card);
        $this->board->addCardToTradeRoute($card);
        return $card;
    }

    public function getTradeRouteCards() {
        return $this->board->getTradeRouteCards();
    }

    public function getActions($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getActions();
    }

    public function getBuys($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getBuys();
    }

    public function getMoney($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getMoney();
    }

    public function isCardOnBoard($card) {
        return $this->board->isCardOnBoard($card);
    }

    public function getBuyCards() {
        $player = $this->getCurrentPlayer();
        $total_money = $player->getMoney();
        $cards = $this->board->getBoardCardsByPrice($this, new Price(), $total_money);
        return getCardNames($cards);
    }

    public function getNumberOfActionsPlayed($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getNumberOfActionsPlayed();
    }

    public function getNumberOfActionsInPlay($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getNumberOfActionsInPlay();
    }

    public function getNumberOfTreasuresPlayed($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getNumberOfTreasuresPlayed();
    }

    public function getAllCards($name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getAllCards();
    }

    public function getVictoryPoints($card, $name = null) {
        $player = $this->getCurrentPlayer($name);
        return $player->getVictoryPoints($this, $card);
    }

    public function numberOfEmptyPiles() {
        return $this->board->numberOfEmptyPiles();
    }

    public function undo($name) {
        $player = $this->getCurrentPlayer($name);
        $player->undo($this);
    }

    public function isGameOver() {
        //return true;

        $gameOver = false;
        if ($this->board->isGameOver())
            $gameOver = true;
        if ($this->allButOnePlayersResigned() && sizeof($this->players) > 1)
            $gameOver = true;
        return $gameOver;
    }

    public function allButOnePlayersResigned() {
        $players_in_game = 0;
        foreach ($this->players as $player) {
            if (!$player->isResigned($this))
                $players_in_game+=1;
        }
        if ($players_in_game < 2) {
            return true;
        }
        return false;
    }

    public function getGameOverStats($cur_player_name) {
// Calculate rating and prepare End of game message

        $res = array();
        $score2player = array();

        foreach ($this->players as $player) {
            $turnNum = $player->getTurnNumber();
            if ($turnNum < RESIGN_MAX) {
                $res['gameAborted'] = true;
                $res['message'] = "Game aborted before turn " . RESIGN_MAX . ", no one wins.";
                return $res;
            }
        }

        $res['gameAborted'] = false;
        $res['logLink'] = "log.php?roomid=" . $this->id;
        foreach ($this->players as $player) {

            $player_res = array();

            $name = $player->getName();
            $turns = $player->getNumberOfTurns();
            list($points, $victory_cards, $victory_tokens) = $player->getVictoryPointsCardsAndTokens($this);
            $vc_tokens = $player->getVictoryTokens();
            if ($player->isResigned($this))
                $points = -100;

            $player_res['name'] = $name;
            $player_res['turns'] = $turns;
            $player_res['points'] = $points;

            $victory_cards_arr = array();
            foreach ($victory_cards as $v_card) {
                $victory_cards_arr[] = $v_card->getData($this);
            }
            $player_res['victory_cards'] = niceReadableString($this, $victory_cards, $name, true);
            $player_res['victory_tokens'] = $victory_tokens;
            $player_res['cards'] = beautifyString(nicePrint($player->getAllCards()));

            $res['players'][] = $player_res;

            $scoreCombination = intval($points * 100 - $turns);
            if (!array_key_exists($scoreCombination, $score2player)) {
                $score2player[$scoreCombination] = array();
            }
            $score2player[$scoreCombination][] = $name;
        }

        krsort($score2player);
        $res['winners'] = array_shift($score2player);

        if (in_array($cur_player_name, $res['winners'])) {
            $res['iWin'] = true;
        } else {
            $res['iWin'] = false;
        }

        return $res;
    }

    public function gameResign($name) {
        $player = $this->getCurrentPlayer($name);
        $turnNum = $player->getTurnNumber();



        if ($player->getPhase() == Phase_GameOver)
            return;

        ScreenDisplay::addScreenMessage($this, true, NEW_LINE, NEW_LINE);
        ScreenDisplay::addScreenMessage($this, true, "leave the game", 'left the game', $name, true, false);

        if ($turnNum < RESIGN_MAX) {
            $player->setPhase($this, Phase_GameResign);
            if ($this->isGameOver()) {
                $this->gameAborted($name);
            }
        } else {
            $player->setPhase($this, Phase_GameResign);
            if ($this->isGameOver()) {
                $this->gameOver($name);
            }
        }
    }

    public function gameAborted($name) {
        $player = $this->getCurrentPlayer($name);
        //means that someone already triggered game over and there is no need to do it again
        if ($player->getPhase() == Phase_GameOver)
            return;

        for ($i = 0; $i < sizeof($this->players); $i++) {
            $player = $this->players[$i];
            $player->setPhase($this, Phase_GameOver);
        }
    }

    public function gameOver($name) {
        $player = $this->getCurrentPlayer($name);
        //means that someone already triggered game over and there is no need to do it again
        if ($player->getPhase() == Phase_GameOver)
            return;

        $line = "Game #" . $this->getId() . " ended.";
        //calculating new rating
        ScreenDisplay::addScreenMessage($this, true, NEW_LINE, NEW_LINE);
        for ($i = 0; $i < sizeof($this->players); $i++) {

            $player = $this->players[$i];

            $user = User::load($player->getName());
            $Ra = $user->getRating();
            $subScore = 0;

            $name = $player->getName();
            $player_turns = $player->getNumberOfTurns();
            list($player_vp, $player_cards, $player_tokens) = $player->getVictoryPointsCardsAndTokens($this);

            $allPlayerCards = $player->getAllCards();
            ScreenDisplay::addScreenMessage($this, true, ": " . nicePrint($allPlayerCards), ": " . ScreenDisplay::$LMSG, $name, true, false);
            if ($player->isResigned($this))
                $player_vp = -100;

            $player->setPhase($this, Phase_GameOver);
            ScreenDisplay::addScreenMessage($this, true, "have $player_vp vp in $player_turns turns", "has $player_vp vp in $player_turns turns", $name, true, false);
            for ($j = 0; $j < sizeof($this->players); $j++) {
                if ($i == $j)
                    continue;

                $opponent = $this->players[$j];
                $opponent_user = User::load($opponent->getName());
                $Rb = $opponent_user->getRating();

                $opponent_name = $opponent->getName();
                list ($opponent_vp, $opponent_cards, $opponent_tokens) = $opponent->getVictoryPointsCardsAndTokens($this);
                $opponent_turns = $opponent->getNumberOfTurns();

                if (($player_vp > $opponent_vp) || ($player_vp == $opponent_vp && $player_turns < $opponent_turns))
                    $win = 1.0;
                else if (($player_vp < $opponent_vp) || ($player_vp == $opponent_vp && $player_turns > $opponent_turns))
                    $win = 0.0;
                else
                    $win = 0.5;

                $subScore += Rating::getAdjustmentToRating($Ra, $Rb, $win);
            }
            $subScore = $subScore / sizeof($this->players);
            $new_rating = $Ra + $subScore;

            $user->setRating($new_rating);
            $user->save();

            $line = $line . " $name  -  vp:$player_vp ($player_turns turns), new rating: " . round($new_rating) . ".";
        }
        $lobby = loadLobby();
        //Chat::addLine($lobby, "System", $line);
    }

    public function runInfluenceCards() {
        foreach ($this->players as $player) {
            $player->runInfluenceCards($this);
        }
    }

    public function setPriceForCard($card, $price) {
        $card = prepare($card);
        $this->board->setPriceForCard($card, $price);
    }

    public static $n2cArray = array();
    public static $c2nArray = array();
    public static $ALL_KINGDOM = array(
        'Adventurer',
        'Bureaucrat',
        'Cellar',
        'Chancellor',
        'Chapel',
        'CouncilRoom',
        'Feast',
        'Festival',
        'Gardens',
        'Laboratory',
        'Library',
        'Market',
        'Militia',
        'Mine',
        'Moat',
        'Moneylender',
        'Remodel',
        'Smithy',
        'Spy',
        'Thief',
        'ThroneRoom',
        'Village',
        'Witch',
        'Woodcutter',
        'Workshop',
        'Baron',
        'Bridge',
        'Conspirator',
        'Coppersmith',
        'Courtyard',
        'Duke',
        'GreatHall',
        'Harem',
        'Ironworks',
        'Masquerade',
        'MiningVillage',
        'Minion',
        'Nobles',
        'Pawn',
        'Saboteur',
        'Scout',
        'SecretChamber',
        'ShantyTown',
        'Steward',
        'Swindler',
        'Torturer',
        'TradingPost',
        'Tribute',
        'Upgrade',
        'WishingWell',
        'FairGrounds',
        'FarmingVillage',
        'FortuneTeller',
        'Hamlet',
        'Harvest',
        'HornOfPlenty',
        'HorseTraders',
        'HuntingParty',
        'Jester',
        'Menagerie',
        'Remake',
        'Tournament',
        'YoungWitch',
        'Bank',
        'Bishop',
        'City',
        'Contraband',
        'CountingHouse',
        'Expand',
        'Forge',
        'Goons',
        'GrandMarket',
        'Hoard',
        'KingsCourt',
        'Loan',
        'Mint',
        'Monument',
        'Mountebank',
        'Peddler',
        'Quarry',
        'Rabble',
        'RoyalSeal',
        'Talisman',
        'TradeRoute',
        'Vault',
        'Venture',
        'Watchtower',
        'WorkersVillage',
        'Ambassador',
        'Bazaar',
        'Caravan',
        'Cutpurse',
        'Embargo',
        'Explorer',
        'FishingVillage',
        'GhostShip',
        'Haven',
        'Island',
        'Lighthouse',
        'Lookout',
        'MerchantShip',
        'NativeVillage',
        'Navigator',
        'Outpost',
        'Pearldiver',
        'PirateShip',
        'Salvager',
        'SeaHag',
        'Smugglers',
        'Tactician',
        'TreasureMap',
        'Treasury',
        'Warehouse',
        'Wharf',
        'Alchemist',
        'Apothecary',
        'Apprentice',
        'Familiar',
        'Golem',
        'Herbalist',
        'PhilosophersStone',
        'Possession',
        'ScryingPool',
        'Transmute',
        'University',
        'Vineyard',
        'BlackMarket',
        'Envoy',
        'Stash',
        'WalledVillage',
        'Governor',
        'BorderVillage',
        'Cache',
        'Cartographer',
        'Crossroads',
        'Develop',
        'Duchess',
        'Embassy',
        'Farmland',
        'FoolsGold',
        'Haggler',
        'Highway',
        'IllGottenGains',
        'Inn',
        'JackOfAllTrades',
        'Mandarin',
        'Margrave',
        'NobleBrigand',
        'NomadCamp',
        'Oasis',
        'Oracle',
        'Scheme',
        'SilkRoad',
        'SpiceMerchant',
        'Stables',
        'Trader',
        'Tunnel',
        'Blogger',
        'Bundle',
        'CheckPoint',
        'Cola',
        'DigitalStore',
        'Geom',
        'Goat',
        'GunShop',
        'Pacifist',
        'Paycheck',
        'Portal',
        'Respawn',
        "Altar",
        "Armory",
        "BandofMisfits",
        "BanditCamp",
        "Beggar",
        "Catacombs",
        "Count",
        "Counterfeit",
        "Cultist",
        "DeathCart",
        "Feodum",
        "Forager",
        "Fortress",
        "Graverobber",
        "Hermit",
        "HuntingGrounds",
        "Ironmonger",
        "JunkDealer",
        "Knights",
        "Marauder",
        "MarketSquare",
        "Mystic",
        "Pillage",
        "PoorHouse",
        "Procession",
        "Rats",
        "Rebuild",
        "Rogue",
        "Sage",
        "Scavenger",
        "Squire",
        "Storeroom",
        "Urchin",
        "Vagrant",
        "WanderingMinstrel",
        "Advisor",
        "Baker",
        "Butcher",
        "CandlestickMaker",
        "Doctor",
        "Herald",
        "Journeyman",
        "Masterpiece",
        "MerchantGuild",
        "Plaza",
        "Soothsayer",
        "Stonemason",
        "Taxman",
    );
    public static $SHELTERS = array(
        "Hovel",
        "Necropolis",
        "OvergrownEstate",
    );
    public static $RUINS = array(
        "AbandonedMine",
        "RuinedLibrary",
        "RuinedMarket",
        "RuinedVillage",
        "Survivors",
    );
    public static $KNIGHTS = array(
        "SirBailey",
        "SirDestry",
        "SirMartin",
        "SirMichael",
        "SirVander",
        "DameAnna",
        "DameJosephine",
        "DameMolly",
        "DameNatalie",
        "DameSylvia",
    );
    public static $ALL_OTHER = array(
        'Copper',
        'Silver',
        'Gold',
        'Platinum',
        'Potion',
        'Estate',
        'Duchy',
        'Province',
        'Colony',
        'Curse'
    );
    public static $PRIZES = array(
        'BagOfGold',
        'Diadem',
        'Followers',
        'Princess',
        'TrustySteed',
        'Duchy'
    );
    public static $DARKAGES_NOT_IN_SUPLY = array(
        "Madman",
        "Mercenary",
        "Spoils",
    );

}

?>